package com.jl.sms.userdata;

import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import com.jl.util.HexUtil;
import com.jl.util.LengthUtil;

public class CommandPacket implements ShortMessage{
	
	//public static final int SPI_NO_COUNTER = 0x00000000;
	public static final int SPI_COUNTER_AVAILABLE = 0x0800;
	public static final int SPI_COUNTER_HIGHER = 0x1000;
	public static final int SPI_COUNTER_ONE_HIGHER = 0x1800;
	//public static final int SPI_NO_CIPHERING = 0x00000000;
	public static final int SPI_CIPHERING = 0x0400;
	//public static final int SPI_NO_RC_CC_DS = 0x00000000;
	public static final int SPI_RC = 0x0100;
	public static final int SPI_CC = 0x0200;
	public static final int SPI_DS = 0x0300;

	//public static final int SPI_NO_POR = 0x00000000;
	public static final int SPI_POR = 0x00000001;
	public static final int SPI_POR_WHEN_ERROR = 0x0002;
	public static final int SPI_POR_RC = 0x0004;
	public static final int SPI_POR_CC = 0x0008;
	public static final int SPI_POR_DS = 0x000C;
	public static final int SPI_POR_CIPHERED = 0x0010;
	public static final int SPI_POR_SMS_DELIVER_REPORT = 0x0000;
	public static final int SPI_POR_SMS_SUBMIT = 0x0020;
	
	//public static final int KIC_DES = 0x01;
	public static final int KIC_DES_CBC = 0x01 | 0x01;
	public static final int KIC_3DES_CBC_2KEY = 0x04 | 0x01;
	public static final int KIC_3DES_CBC_3KEY = 0x08 | 0x01;
	public static final int KIC_DES_ECB = 0x0C | 0x01;
	
	public static final int KID_DES_CBC = 0x01 | 0x01;
	public static final int KID_3DES_CBC_2KEY = 0x04 | 0x01;
	public static final int KID_3DES_CBC_3KEY = 0x08 | 0x01;

	public static final int KEY_INDICATION_0 = 0x00 << 4;
	public static final int KEY_INDICATION_1 = 0x01 << 4;
	public static final int KEY_INDICATION_2 = 0x02 << 4;
	public static final int KEY_INDICATION_3 = 0x03 << 4;
	public static final int KEY_INDICATION_4 = 0x04 << 4;
	public static final int KEY_INDICATION_5 = 0x05 << 4;
	public static final int KEY_INDICATION_6 = 0x06 << 4;
	public static final int KEY_INDICATION_7 = 0x07 << 4;
	public static final int KEY_INDICATION_8 = 0x08 << 4;
	public static final int KEY_INDICATION_9 = 0x09 << 4;
	public static final int KEY_INDICATION_10 = 0x0A << 4;
	public static final int KEY_INDICATION_11 = 0x0B << 4;
	public static final int KEY_INDICATION_12 = 0x0C << 4;
	public static final int KEY_INDICATION_13 = 0x0D << 4;
	public static final int KEY_INDICATION_14 = 0x0E << 4;
	public static final int KEY_INDICATION_15 = 0x0F << 4;
	

	/**Command Packet Identifier*/
	private String cpi = "";
	/**Command Packet Length,0 is default "length of BER-TLV data objects"*/
	private int cpl = 2;
	/**Command Header Identifier*/
	private String chi = "";
	/**Command Header Length,0 is default "length of BER-TLV data objects"*/
	private int chl = 1;
	/** Security Parameters Indication */
	private String spi;
	/** Key and algorithm Identifier for ciphering */
	private String kic;
	/** Key and algorithm Identifier for RC/CC/DS */
	private String kid;
	/** ciphering key value */
	private String keyc;
	/** CC key value */
	private String keyd;
	/** Toolkit Application Reference */
	private String tar;
	/** Counter */
	private String cntr;
	/** Security data */
	private byte[] data;
	/** Short Message */
	private byte[] command;
	/** Padding Counter */
	private String pcntr = "00";
	/** RC/CC/DS value */
	private String rcccds = "";
	/** RC/CC/DS length */
	private int rcccdsLen = 0x08;

	public static class Builder {

		private String cpi = "";
		private int cpl = 2;
		private String chi = "";
		private int chl = 1;
		private String spi;
		private String kic;
		private String kid;
		private String keyc;
		private String keyd;
		private String tar = "00 00 00";
		private String cntr = "00 00 00 00 00";
		private byte[] data;

		public Builder() {
		}

		public Builder(String data) {
			this.data = HexUtil.parseHexToByteArray(data);
		}

		public Builder(byte[] data) {
			this.data = data.clone();
		}

		public Builder CPI(String cpi) {
			this.cpi = cpi;
			return this;
		}

		/**
		 * Command Packet Length,0 is default "length of BER-TLV data objects"
		 * @param cpl
		 * @return
		 */
		public Builder CPL(int cpl) {
			this.cpl = cpl;
			return this;
		}

		public Builder CHI(String chi) {
			this.chi = chi;
			return this;
		}

		/**
		 * Command Header Length,0 is default "length of BER-TLV data objects"
		 * @param chl
		 * @return
		 */
		public Builder CHL(int chl) {
			this.chl = chl;
			return this;
		}

		public Builder SPI(String spi) {
			this.spi = spi;
			return this;
		}
		
		public Builder SPI(int spi){
			this.spi = HexUtil.toHex(spi, 2);
			return this;
		}
		

		public Builder KIC(String kic, String keyValue) {
			this.kic = kic;
			this.keyc = keyValue;
			return this;
		}
		
		public Builder KIC(int kic, String keyValue) {
			this.kic = HexUtil.toHexXX(kic);
			this.keyc = keyValue;
			return this;
		}

		public Builder KID(String kid, String keyValue) {
			this.kid = kid;
			this.keyd = keyValue;
			return this;
		}
		
		public Builder KID(int kid, String keyValue) {
			this.kid = HexUtil.toHexXX(kid);
			this.keyd = keyValue;
			return this;
		}
		
		public Builder TAR(String tar) {
			this.tar = tar;
			return this;
		}

		public Builder CNTR(String cntr) {
			this.cntr = cntr;
			return this;
		}

		public CommandPacket build() {
			if (spi == null) {
				spi = "0000";
			}
			if (kic == null) {
				kic = "00";
			}
			if (kid == null) {
				kid = "00";
			}
			return new CommandPacket(cpi, cpl, chi, chl, spi, kic, kid, keyc,
					keyd, tar, cntr, data);
		}
	}

	/**
	 * just used for ScriptableObject
	 */
	public CommandPacket() {
	};

	private CommandPacket(String cpi, int cpl, String chi, int chl,
			String spi, String kic, String kid, String keyc, String keyd,
			String tar, String cntr, byte[] data) {
		this.cpi = cpi;
		this.cpl = cpl;
		this.chi = chi;
		this.chl = chl;
		this.spi = spi;
		this.kic = kic;
		this.kid = kid;
		this.keyc = keyc;
		this.keyd = keyd;
		this.tar = tar;
		this.cntr = cntr;
		this.data = data.clone();
		try {
			init();
		} catch (GeneralSecurityException e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	private void init() throws GeneralSecurityException {
		String hexData = HexUtil.toHexString(data);
		Cipher cipher = getCipher(Cipher.ENCRYPT_MODE);
		Cipher checksum = getCryptChecksum();
		if (cipher != null) {
			String forCipher = cntr + pcntr + hexData;
			int count = HexUtil.getHexCount(forCipher);
			if (checksum != null) {
				count += rcccdsLen;
			}
			count = (8 - (count % 8)) % 8;
			for (int i = 0; i < count; i++) {
				hexData += "00 ";
			}
			pcntr = HexUtil.toHexXX(count);
		} else {
			pcntr = "00";
		}
		if (checksum != null) {
			// sx bx spi kic kid cntr pcntr (mac) data
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < rcccdsLen; i++) {
				sb.append("00");
			}
			String temp = sb.toString();
			String header = spi + kic + kid + tar + cntr + pcntr;
			String forMac = LengthUtil.bx(chi + " bx " + header + temp);
			forMac = LengthUtil.sx(cpi + "sx " + forMac + hexData);
			forMac = HexUtil.subHex(forMac, 0,
					3 + HexUtil.getHexCount(chi + cpi))
					+ header + hexData;//
			this.rcccds = new DesMac(forMac, checksum, rcccdsLen).getMac();
		} else {
			this.rcccds = "";
		}
		String finalData = cpi + "sx ";
		String header = "bx " + this.spi + this.kic + this.kid + this.tar
				+ this.cntr + this.pcntr + this.rcccds;
		String headerlen = LengthUtil.bx(header).substring(0, 2);
		finalData += chi + headerlen + this.spi + this.kic + this.kid
				+ this.tar;

		String forCipher = this.cntr + this.pcntr + this.rcccds + hexData;
		if (cipher != null) {
			// cntr pcntr mac data
			String cryptData = HexUtil.toHexString(cipher.doFinal(HexUtil
					.parseHexToByteArray(forCipher)));
			finalData += cryptData;
		} else {
			finalData += forCipher;
		}
		this.command = HexUtil.parseHexToByteArray(LengthUtil.sx(finalData));
	}

	/**
	 * get RC/CC/DS
	 * 
	 * @return
	 */
	public byte[] getChecksum() {
		return HexUtil.parseHexToByteArray(this.rcccds);
	}

	/**
	 * get the real short message that to be send
	 * 
	 * @return
	 */
	public byte[] getCommand() {
		return this.command;
	}

	/**
	 * parse the spi and kic ,return the desctiption of the ctypt algrithm.
	 * 
	 * @param spi
	 * @param kic
	 * @return
	 */
	public String getKicDescribe() {
		String res = "";
		if ((HexUtil.parseHexToByte(HexUtil.subHex(spi, 0, 1)) & 0x04) == 0x04) {
			res += "ciphering:";
			res += parseKIByte(kic, false);
		} else {
			res += "no ciphering";
		}
		return res;
	}

	/**
	 * parse the spi and kid ,return the desctiption of the signature algrithm.
	 * 
	 * @param spi
	 * @param kid
	 * @return
	 */
	public String getKidDescribe(String spi, String kid) {
		String res = "";
		switch (HexUtil.parseHexToByte(HexUtil.subHex(spi, 0, 1)) & 0x03) {
		case 0x00:
			res += "No RC, CC or DS";
			break;
		case 0x01:
			res += "RC:";
			break;
		case 0x02:
			res += "CC:";
			res += parseKIByte(kid, true);
			break;
		case 0x03:
			res += "DS:";
			break;
		}
		return res;
	}

	private Cipher getCipher(int mode) throws NoSuchAlgorithmException,
			NoSuchPaddingException, InvalidKeyException,
			InvalidAlgorithmParameterException {
		byte bspi = HexUtil.parseHexToByte(spi.trim().substring(0, 2));
		if ((bspi & 0x04) == 0x04) {
			byte bkic = HexUtil.parseHexToByte(kic.trim());
			switch (bkic & 0x03) {
			case 0x01:
				return getDesCipher(mode, bkic, "kic");
			case 0x00:// Algorithm known implicitly by both entities
			case 0x02:// Reserved
			case 0x03:// proprietary Implementations
				return null;
			}
		} else {
			return null;
		}
		return null;
	}

	private Cipher getCryptChecksum() throws NoSuchAlgorithmException,
			NoSuchPaddingException, InvalidKeyException,
			InvalidAlgorithmParameterException {
		byte bspi = HexUtil.parseHexToByte(spi.trim().substring(0, 2));
		switch (bspi & 0x03) {
		case 0x02:
			byte bkid = HexUtil.parseHexToByte(kid);
			return getDesCipher(Cipher.ENCRYPT_MODE, bkid, "kid");
		case 0x00:
		case 0x01:
		case 0x03:
			return null;
		}
		return null;
	}

	private Cipher getDesCipher(int mode, byte bkic, String type)
			throws NoSuchAlgorithmException, NoSuchPaddingException,
			InvalidKeyException, InvalidAlgorithmParameterException {
		String cipherDescript = "DES";
		String name = "DES";
		boolean isCbc = true;
		switch (bkic & 0x0c) {
		case 0x00:
			cipherDescript += "/CBC";
			break;
		case 0x04:
			cipherDescript += "ede/CBC";
			name += "ede";
			motifyKey16To24(type);
			break;
		case 0x08:
			cipherDescript += "ede/CBC";
			name += "ede";
			motifyKey16To24(type);
			break;
		case 0x0C:
			if (type.equalsIgnoreCase("lid")) {
				return null;
			}
			cipherDescript += "/ECB";
			isCbc = false;
			break;
		}
		cipherDescript += "/NoPadding";
		Cipher cipher = Cipher.getInstance(cipherDescript);

		if (cipher != null) {
			SecretKeySpec ksp = null;
			if (isCbc) {
				IvParameterSpec ivp = new IvParameterSpec(new byte[] { 0, 0, 0,
						0, 0, 0, 0, 0, });
				if (type.equalsIgnoreCase("kic")) {
					ksp = new SecretKeySpec(HexUtil.parseHexToByteArray(keyc),
							name);
					cipher.init(mode, ksp, ivp);
				} else {
					ksp = new SecretKeySpec(HexUtil.parseHexToByteArray(keyd),
							name);
					cipher.init(mode, ksp, ivp);
				}
			} else {
				if (type.equalsIgnoreCase("kic")) {
					ksp = new SecretKeySpec(HexUtil.parseHexToByteArray(keyc),
							name);
					cipher.init(mode, ksp);
				} else {
					ksp = new SecretKeySpec(HexUtil.parseHexToByteArray(keyd),
							name);
					cipher.init(mode, ksp);
				}
			}
		}
		return cipher;
	}

	private void motifyKey16To24(String type) {
		if (type.trim().equalsIgnoreCase("kic")) {
			if (HexUtil.getHexCount(keyc) == 16)
				this.keyc = this.keyc + HexUtil.compact(keyc).substring(0, 16);
		} else {
			if (HexUtil.getHexCount(keyd) == 16)
				this.keyd = this.keyd + HexUtil.compact(keyd).substring(0, 16);
		}
	}

	private static String parseKIByte(String ki, boolean iskid) {
		String res = "";
		switch (HexUtil.parseHexToByte(ki) & 0x03) {
		case 0x00:
			res += "both konwn";
			break;
		case 0x01:
			res += getDESDescribe(ki, iskid);
			break;
		case 0x02:
			res += "reserve";
			break;
		case 0x03:
			res += "proprietary";
			break;
		}
		return res;
	}

	private static String getDESDescribe(String k, boolean iskid) {
		String res = "";
		switch (HexUtil.parseHexToByte(k) & 0x0C) {
		case 0x00:
			res += "DES CBC";
			break;
		case 0x04:
			res += "3DES2KEY CBC";
			break;
		case 0x08:
			res += "3DES3KEY CBC";
			break;
		case 0x0C:
			if (iskid) {
				res += "Reserved";
			} else {
				res += "DES ECB";
			}
			break;
		}
		return res;
	}

	private class DesMac {
		private byte[] array;
		Cipher cipher;
		int len = 0;

		public DesMac(String hex, Cipher cipher, int len) {
			int count = HexUtil.getHexCount(hex);
			count = (8 - (count % 8)) % 8;
			for (int i = 0; i < count; i++) {
				hex += "00 ";
			}
			array = HexUtil.parseHexToByteArray(hex);
			this.len = len;
			this.cipher = cipher;
		}

		public String getMac() throws GeneralSecurityException {
			byte[] res = cipher.doFinal(array);
			res = Arrays.copyOfRange(res, res.length - len, res.length);
			return HexUtil.toHexString(res);
		}
	}

	@Override
	public byte[] value() {
		return getCommand();
	}

	@Override
	public int headerLength() {
		return 0;
	}

}
